package com.asdf.entities;


import java.util.Date;
import java.util.List;

import com.asdf.db.DataBase;
import com.asdf.db.DataBase.DataSource;
import com.asdf.db.operations.companyclub.GetCompanyClubOp;
import com.asdf.db.operations.user.GetUserByUsernameOp;
import com.asdf.db.results.ResultStatus;
import com.asdf.db.results.companyclub.CompanyClubResult;
import com.asdf.db.results.user.UserResult;
import com.asdf.entities.Airport;
import com.asdf.entities.CreditCard;
import com.asdf.external.DBUsageDetails;
import com.asdf.external.DBUserDetails;

/** This class implements a connection.
 * @author so4i2014
 *
 */
public class Connection
{
	private static long MINUTE_IN_MILLISECONDS = 1000 * 60;
	
	/** custom clock interface */
	public interface Clock {
		public Date now();
	}
	
	/** normal clock to be used by {@link Connection} by default,
	 *  returns real system time */
	static class RealTime implements Clock {
		public Date now() {
			return new Date();
		}
	}
	static private Clock clock = new RealTime();
	/** allow the user to substitute custom clock to be used by Connection class
	 * (for testing purposes)
	 * @param c should implement {@link Connection.Clock} interface
	 */
	static public void setCustomClock(Clock c) {
		clock = c;
	}
	
	
	private DBUsageDetails bill = null;
	private DBUserDetails user = null;
	private Airport airport = null;
	private CreditCard card = null;
	
	/** Creates new connection in the airport.
	 * @param airport - the airport
	 */
	public Connection(Airport airport)
	{
		super();
		this.airport = airport;
	}
	
	/** Try to set user for the connection. 
	 * @param username - username (login).
	 * @param password - user password.
	 * @return If the user is not subscribed or the password is not correct,
	 * the function will return false. Else true.
	 */
	@SuppressWarnings("unchecked")
	public boolean setUser(String username, String password)
	{
		GetUserByUsernameOp op = new GetUserByUsernameOp((List<DBUserDetails>)
				DataBase.getInstance().getDataSource(DataSource.UserList));
		UserResult res = null;
		
		op.setUsername(username);
		res = (UserResult)DataBase.getInstance().Do(op);
		if (res.getStatus() != ResultStatus.OK)
			return false;
		
		this.user = res.getUser();
		if (!this.user.getPassword().equals(password))
			return false;
		
		return true;
	}

	public void setCard( CreditCard card)
	{
		this.card = card;
	}
	
	public void start()
	{
		this.bill = new DBUsageDetails();
		this.bill.setDate(clock.now());
	}

	/** Ends the connection and charges the credit card if necessary.
	 * @param chargeTime - the duration of the connection in minutes.
	 * @return DBUsage corresponding to this connection.
	 */
	@SuppressWarnings("unchecked")
	public DBUsageDetails endAndGetUsageDetails(int chargeTime)
	{
		this.bill.setUserId(this.user.getId());
		this.bill.setAirportId(this.airport.getId());
		this.bill.setDuration(chargeTime);
		
		// Charging the card.
		if (this.card != null)
		{
			float discount = 0.0f;
			float rate = (float)this.airport.getRate();
			
			// Checking whether the user has any discount.
			if (this.user.getAviationClubMember())
			{
				GetCompanyClubOp op = new GetCompanyClubOp(
					(List<CompanyClub>)DataBase.getInstance().
					getDataSource(DataSource.CompanyClubs));
				CompanyClubResult res = null;
				
				op.setCompanyName(this.user.getAviationCompanyName());
				// Getting the discount
				res = (CompanyClubResult)DataBase.getInstance().Do(op);
				if (res.getStatus() == ResultStatus.OK)
					discount = (float)res.getCompany().getDiscount();
			}
			// Charging
			this.card.sendForCharge(((1 - discount) * rate) * chargeTime);
		}
		return this.bill;
	}
	
	/** Get the duration of the connection in seconds.
	 * @return the duration in seconds.
	 */
	public long getDuration()
	{
		Date now = clock.now();
		
		if (bill == null)
			return -1;
		
		// convert milliseconds to minutes and return the difference
		return (now.getTime( ) - bill.getDate( ).getTime( )) /
			MINUTE_IN_MILLISECONDS;
	}

}
